package com.dl698.asn1.axdr;

import com.dl698.util.ReverseByteArrayInputStream;
import com.dl698.util.ReverseByteArrayOutputStream;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * AXDR序列类型抽象类
 * 实现序列类型的编解码功能
 * 
 * @author wind
 * @version 1.1.1
 */
public abstract class AxdrSequenceOf<E extends AxdrType> implements AxdrType {

    protected byte[] dataCode = null;
    protected int length = -1;
    protected List<E> seqOf = null;

    /**
     * 默认构造函数
     */
    public AxdrSequenceOf() {
        this.seqOf = new ArrayList<>();
    }

    /**
     * 构造函数，指定元素类型
     * 
     * @param elementType 元素类型
     */
    public AxdrSequenceOf(Class<E> elementType) {
        this.seqOf = new ArrayList<>();
    }

    /**
     * 构造函数，指定元素列表
     * 
     * @param seqOf 元素列表
     */
    public AxdrSequenceOf(List<E> seqOf) {
        this.seqOf = seqOf;
    }

    /**
     * 设置数据编码
     * 
     * @param dataCode 数据编码
     */
    public void setDataCode(byte[] dataCode) {
        this.dataCode = dataCode;
        this.seqOf = new ArrayList<>();
    }

    /**
     * 设置序列
     * 
     * @param seqOf 序列
     */
    public void setSeq(List<E> seqOf) {
        this.seqOf = seqOf;
    }

    /**
     * 设置长度
     * 
     * @param length 长度
     */
    public void setLength(int length) {
        this.length = length;
        this.seqOf = new ArrayList<>(length);
    }

    /**
     * 获取数据编码
     * 
     * @return 数据编码
     */
    public byte[] getDataCode() {
        if (this.dataCode != null) {
            return this.dataCode;
        } else {
            throw new RuntimeException("dataCode is null");
        }
    }

    /**
     * 获取序列大小
     * 
     * @return 序列大小
     */
    public int size() {
        return seqOf.size();
    }

    /**
     * 获取指定索引的元素
     * 
     * @param index 索引
     * @return 元素
     */
    public E get(int index) {
        return seqOf.get(index);
    }

    /**
     * 添加元素
     * 
     * @param element 元素
     */
    public void add(E element) {
        seqOf.add(element);
    }

    /**
     * 创建列表元素
     * 
     * @return 列表元素
     */
    protected abstract E createListElement();

    @Override
    public int decode(ReverseByteArrayInputStream input) {
        int codeLength = 0;
        int numElements = this.length == -1 ? 0 : this.length;

        if (numElements == 0) {
            AxdrLength l = new AxdrLength(0);
            codeLength += l.decode(input);

            numElements = l.getValue();
        }

        this.seqOf = new ArrayList<>();

        for (int i = 0; i < numElements; i++) {
            E subElem = this.createListElement();
            codeLength += subElem.decode(input);
            this.seqOf.add(subElem);
        }

        return codeLength;
    }

    @Override
    public int encode(ReverseByteArrayOutputStream output) {
        int codeLength;

        if (this.dataCode != null) {
            codeLength = this.dataCode.length;
            try {
                for (int i = this.dataCode.length - 1; i >= 0; i--) {
                    output.write(this.dataCode[i]);
                }
            } catch (IOException e) {
                throw new RuntimeException("Error writing dataCode to output stream", e);
            }
        } else {
            if (this.length != -1 && this.length != this.seqOf.size()) {
                throw new RuntimeException("Error decoding AxdrSequenceOf: Size of elements does not match.");
            }

            codeLength = 0;
            for (int i = (this.seqOf.size() - 1); i >= 0; i--) {
                codeLength += this.seqOf.get(i).encode(output);
            }

            if (this.length == -1) {
                try {
                    codeLength += AxdrLength.encodeLength(output, this.seqOf.size());
                } catch (IOException e) {
                    throw new RuntimeException("Error encoding AxdrLength", e);
                }
            }

        }
        return codeLength;
    }

    /**
     * 编码并保存
     * 
     * @param encodingSizeGuess 编码大小预估
     */
    public void encodeAndSave(int encodingSizeGuess) {
        ReverseByteArrayOutputStream revOStream = new ReverseByteArrayOutputStream();
        revOStream.setBufSize(encodingSizeGuess);
        try {
            encode(revOStream);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Error encoding AxdrSequenceOf", e);
        }
        this.dataCode = revOStream.getArray();
    }

    @Override
    public String toString() {
        return "AxdrSequenceOf{" +
                "seqOf=" + seqOf +
                '}';
    }
}